﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace NTypeClasses
{
	/// <summary>
	/// Some useful functions to compute assembly dependency graph.
	/// </summary>
	public static class AssemblyDependencyHelper
	{
		static readonly Dictionary<Assembly, HashSet<Assembly>> known = new Dictionary<Assembly, HashSet<Assembly>>();
		/// <summary>
		/// Tests if an assembly depends on another assembly.
		/// </summary>
		/// <param name="what">An assembly to test dependecy.</param>
		/// <param name="dependsOn">Potential dependency.</param>
		/// <returns>true if what depends on dependsOn, else false</returns>
		public static bool DependsOn(this Assembly what, Assembly dependsOn)
		{
			lock (known) {
				HashSet<Assembly> references = ComputeReferences(what);

				return references.Contains(dependsOn);
			}
		}

		private static HashSet<Assembly> ComputeReferences(Assembly what)
		{
			HashSet<Assembly> references;
			if (!known.TryGetValue(what, out references)) {
				references = new HashSet<Assembly>();
				known[what] = references;
				var temp = new HashSet<Assembly>();
				foreach (var name in what.GetReferencedAssemblies()) {
					var assembly = FindAssembly(name);
					if (assembly == null) continue;
					if (temp.Add(assembly))
						temp.UnionWith(ComputeReferences(assembly));
				}
				references.UnionWith(temp);
			}

			return references;
		}

		/// <summary>
		/// Get all assemblies which specified assembly depends on.
		/// </summary>
		/// <param name="assembly">An assembly to get dependencies for.</param>
		/// <returns>An array of all assembly's dependencies (including non-direct ones).</returns>
		public static Assembly[] GetReferences(Assembly assembly)
		{
			lock (known) {
				return ComputeReferences(assembly).ToArray();
			}
		}

		/// <summary>
		/// Finds a loaded assembly using its name.
		/// </summary>
		/// <param name="name">A name for assembly to find.</param>
		/// <returns>An assembly with specified name, or null if nothing was found.</returns>
		public static Assembly FindAssembly(AssemblyName name)
		{
			if (name == null) throw new ArgumentNullException("name");

			return AppDomain.CurrentDomain.GetAssemblies()
				.FirstOrDefault(assembly => name.FullName == assembly.GetName().FullName);
		}
	}
}
